package kz.pompei.ifacer.visio.skin;

import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.swing.JComponent;

import kz.pompei.ifacer.dialogs.FontUtil;
import kz.pompei.ifacer.grot.CommonField;
import kz.pompei.ifacer.grot.acts.Act;
import kz.pompei.ifacer.grot.acts.ChangeFieldsAct;
import kz.pompei.ifacer.model.CursorInfo;
import kz.pompei.ifacer.model.PaintKind;
import kz.pompei.ifacer.model.widgets.Widget;
import kz.pompei.ifacer.model.widgets.WidgetButton;
import kz.pompei.ifacer.model.widgets.WidgetCheck;
import kz.pompei.ifacer.model.widgets.WidgetForm;
import kz.pompei.ifacer.model.widgets.WidgetGrid;
import kz.pompei.ifacer.model.widgets.WidgetLabel;
import kz.pompei.ifacer.model.widgets.WidgetRect;
import kz.pompei.ifacer.visio.Visio;
import kz.pompei.ifacer.visio.WidgetProp;
import kz.pompei.ifacer.visio.skin.paint.PaintWidgetButton;
import kz.pompei.ifacer.visio.skin.paint.PaintWidgetForm;
import kz.pompei.ifacer.visio.skin.paint.PaintWidgetLabel;
import kz.pompei.ifacer.visio.skin.paint.check.PaintWidgetCheck;
import kz.pompei.ifacer.visio.skin.paint.grid.PaintWidgetGrid;

public class SkinVisio implements Visio {
  
  private final Map<Class<?>, Object> paintWidgetMap = new HashMap<>();
  
  public SkinVisio(SkinGetter skinGetter) {
    paintWidgetMap.put(WidgetForm.class, new PaintWidgetForm(skinGetter));
    paintWidgetMap.put(WidgetLabel.class, new PaintWidgetLabel(skinGetter));
    paintWidgetMap.put(WidgetButton.class, new PaintWidgetButton(skinGetter));
    paintWidgetMap.put(WidgetGrid.class, new PaintWidgetGrid(skinGetter));
    paintWidgetMap.put(WidgetCheck.class, new PaintWidgetCheck(skinGetter));
  }
  
  @Override
  public void paint(Graphics2D g, Widget widget, PaintKind paintKind, Point mouse,
      Rectangle allSpace) throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    FontUtil.prepare(g);
    
    if (mouse == null) mouse = new Point(-1000, -1000);
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("paint", widget);
    
    Method meth = painter.getClass().getMethod("paint", Graphics2D.class, widget.getClass(),
        PaintKind.class, Point.class, Rectangle.class);
    
    meth.invoke(painter, g, widget, paintKind, mouse, allSpace);
  }
  
  @Override
  public CursorInfo getCursorInfo(Graphics2D g, Widget widget, Point mouse) throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("getCursorInfo", widget);
    
    Method meth = painter.getClass().getMethod("getCursorInfo", Graphics2D.class,
        widget.getClass(), Point.class);
    
    return (CursorInfo)meth.invoke(painter, g, widget, mouse);
  }
  
  @Override
  public Act setProperty(Graphics2D g, Widget widget, WidgetProp prop, int value) throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("setProperty", widget);
    
    try {
      Method meth = painter.getClass().getMethod("setProperty", Graphics2D.class,
          widget.getClass(), WidgetProp.class, Integer.TYPE);
      {
        Act ret = (Act)meth.invoke(painter, g, widget, prop, value);
        if (ret != null) return ret;
      }
    } catch (NoSuchMethodException e) {}
    
    {
      return setPropertyDefault(g, widget, prop, value);
    }
  }
  
  private Act setPropertyDefault(Graphics2D g, Widget widget, WidgetProp prop, int value)
      throws Exception {
    switch (prop) {
    case LEFT:
      return new ChangeFieldsAct(widget.uuid, "x", value);
    case TOP:
      return new ChangeFieldsAct(widget.uuid, "y", value);
    case WIDTH: {
      if (widget instanceof WidgetRect) {
        return new ChangeFieldsAct(widget.uuid, "width", value);
      }
      return null;
    }
    case HEIGHT: {
      if (widget instanceof WidgetRect) {
        return new ChangeFieldsAct(widget.uuid, "height", value);
      }
      return null;
    }
    }
    
    Rectangle rect = getWidgetBound(g, widget);
    
    switch (prop) {
    case RIGHT:
      return new ChangeFieldsAct(widget.uuid, "x", value - rect.width);
    case BOTTOM:
      return new ChangeFieldsAct(widget.uuid, "y", value - rect.height);
    }
    
    throw new IllegalArgumentException("Unknown prop = " + prop);
  }
  
  @Override
  public Rectangle getWidgetBound(Graphics2D g, Widget widget) throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("getWidgetBound", widget);
    
    Method meth = painter.getClass().getMethod("getWidgetBound", Graphics2D.class,
        widget.getClass());
    
    return (Rectangle)meth.invoke(painter, g, widget);
  }
  
  @Override
  public Act move(Graphics2D g, Widget original, Widget copy, Point fromPoint, Point toPoint)
      throws Exception {
    if (original == null) throw new IllegalArgumentException("original == null");
    if (copy == null) throw new IllegalArgumentException("copy == null");
    if (!original.getClass().equals(copy.getClass())) {
      throw new IllegalArgumentException("Different classes: original " + original.getClass()
          + ", copy " + copy.getClass());
    }
    
    Object painter = paintWidgetMap.get(original.getClass());
    if (painter == null) throw noPainter("move", original);
    
    Method meth = painter.getClass().getMethod("move", Graphics2D.class, original.getClass(),
        copy.getClass(), Point.class, Point.class);
    
    return (Act)meth.invoke(painter, g, original, copy, fromPoint, toPoint);
  }
  
  @Override
  public Act getDoubleClickAct(Graphics2D g, Widget widget, Point mouse, JComponent owner)
      throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("getDoubleClickAct", widget);
    
    try {
      Method meth = painter.getClass().getMethod("getDoubleClickAct", Graphics2D.class,
          widget.getClass(), Point.class, JComponent.class);
      
      return (Act)meth.invoke(painter, g, widget, mouse, owner);
    } catch (NoSuchMethodException e) {//Widget must not need double click behavior
      return null;
    }
  }
  
  @Override
  public Set<CommonField> getChangableFields(Graphics2D g, Widget widget, Point mouse)
      throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("getChangableFields", widget);
    
    try {
      Method meth = painter.getClass().getMethod("getChangableFields", Graphics2D.class,
          widget.getClass(), Point.class);
      
      @SuppressWarnings("unchecked")
      Set<CommonField> ret = (Set<CommonField>)meth.invoke(painter, g, widget, mouse);
      return ret;
    } catch (NoSuchMethodException e) {
      return Collections.emptySet();
    }
  }
  
  private Exception noPainter(String method, Widget widget) {
    return new IllegalArgumentException("No painter in " + method + " for widget of class "
        + widget.getClass());
  }
  
  @Override
  public Object getFieldValue(Graphics2D g, CommonField cf, Widget widget, Point mouse)
      throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("getFieldValue", widget);
    
    Method meth = painter.getClass().getMethod("getFieldValue", Graphics2D.class,
        CommonField.class, widget.getClass(), Point.class);
    
    return meth.invoke(painter, g, cf, widget, mouse);
  }
  
  @Override
  public Act setFieldValue(Graphics2D g, Widget widget, CommonField cf, Object fieldValue,
      Point mouse) throws Exception {
    if (widget == null) throw new IllegalArgumentException("widget == null");
    
    Object painter = paintWidgetMap.get(widget.getClass());
    if (painter == null) throw noPainter("setFieldValue", widget);
    
    Method meth = painter.getClass().getMethod("setFieldValue", Graphics2D.class,
        widget.getClass(), CommonField.class, Object.class, Point.class);
    
    return (Act)meth.invoke(painter, g, widget, cf, fieldValue, mouse);
  }
}
